using System.Collections.Generic;
using System.IO;
using System.Text;

namespace BLGenerator.BusinessLogic.FileGenerators
{
    public sealed class EntityFileGenerator
    {
        public static void Generate(Entity entity, string filename)
        {
            StreamWriter stream = new FileInfo(filename).CreateText();
            CompositeFileBlock fileBlock = new CompositeFileBlock();
            fileBlock.Add("using System;");
            fileBlock.Add("using System.Data;");
            fileBlock.Add("using System.Data.SqlClient;");
            fileBlock.Add(string.Format("using {0};", entity.Project.Namespace));
            fileBlock.Add();
            fileBlock.Add("namespace " + entity.Project.Namespace + ".Entities");
            fileBlock.Add("{");
            fileBlock.Add(FileBlock_Class(entity));
            fileBlock.Add("}");
            fileBlock.WriteToStream(stream);
            stream.Close();
        }

        private static IFileBlock FileBlock_Class(Entity entity)
        {
            CompositeFileBlock fileBlock = new CompositeFileBlock();
            fileBlock.Add("public partial class " + entity.EntityName);
            fileBlock.Add("{");
            fileBlock.Add(FileBlock_Fields(entity));
            fileBlock.Add();
            fileBlock.Add(FileBlock_Properties(entity));
            fileBlock.Add();
            fileBlock.Add(FileBlock_Constructors(entity));
            fileBlock.Add();
            fileBlock.Add(FileBlock_FillFromReaderMethod(entity));
            fileBlock.Add();
            fileBlock.Add(FileBlock_GetWithPKMethod(entity));
            fileBlock.Add();
            fileBlock.Add(FileBlock_SaveMethod(entity));
            fileBlock.Add("}");
            return fileBlock;
        }

        private static IFileBlock FileBlock_Fields(Entity entity)
        {
            CompositeFileBlock fileBlock = new CompositeFileBlock();
            fileBlock.Add("#region Private fields");
            fileBlock.Add("private bool _isNew;");
            foreach (Field field in entity.FieldCollection)
            {
                fileBlock.Add(string.Format("private {0} {1};", field.TypeName, field.FieldName));
            }
            fileBlock.Add("#endregion");
            return fileBlock;
        }

        private static IFileBlock FileBlock_Properties(Entity entity)
        {
            string tab = FileGeneratorsHelper.Tab;
            CompositeFileBlock fileBlock = new CompositeFileBlock();
            fileBlock.Add("#region Properties");
            foreach (Field field in entity.FieldCollection)
            {
                fileBlock.Add(string.Format("public {0} {1}", field.TypeName, field.PropertyName));
                fileBlock.Add("{");
                fileBlock.Add(tab + "get");
                fileBlock.Add(tab + "{");
                fileBlock.Add(tab + tab + string.Format("return {0};", field.FieldName));
                fileBlock.Add(tab + "}");
                if (!field.IsAutoGenerated)
                {
                    fileBlock.Add(tab + "set");
                    fileBlock.Add(tab + "{");
                    fileBlock.Add(tab + tab + string.Format("{0} = value;", field.FieldName));
                    fileBlock.Add(tab + "}");
                }
                fileBlock.Add("}");
            }
            fileBlock.Add("#endregion");
            return fileBlock;
        }

        private static IFileBlock FileBlock_Constructors(Entity entity)
        {
            CompositeFileBlock fileBlock = new CompositeFileBlock();

            fileBlock.Add("#region Constructors");

            fileBlock.Add(string.Format("public {0}(SqlDataReader reader)", entity.EntityName) );
            fileBlock.Add("{");
            fileBlock.Add(1, "FillFromFullReader(reader);");
            fileBlock.Add(1, "_isNew = false;");
            fileBlock.Add("}");

            StringBuilder stringBuilder = new StringBuilder();
            foreach (Field field in entity.FieldCollection)
            {
                if (field.IsAutoGenerated) continue;
                stringBuilder.Append(",");
                stringBuilder.Append(field.TypeName);
                stringBuilder.Append(" ");
                stringBuilder.Append(field.ParameterName);
            }
            fileBlock.Add(string.Format("public {0}({1})", entity.EntityName, stringBuilder.ToString().Substring(1)));
            fileBlock.Add("{");

            foreach (Field field in entity.FieldCollection)
            {
                if (field.IsAutoGenerated) continue;
                fileBlock.Add(1, string.Format("{0} = {1};", field.FieldName, field.ParameterName));
            }
            fileBlock.Add(1, "_isNew = true;");

            fileBlock.Add("}");

            fileBlock.Add("#endregion");

            return fileBlock;
        }

        private static IFileBlock FileBlock_FillFromReaderMethod(Entity entity)
        {
            CompositeFileBlock fileBlock = new CompositeFileBlock();

            fileBlock.Add("protected void FillFromFullReader(SqlDataReader reader)");
            fileBlock.Add("{");
            foreach (Field field in entity.FieldCollection)
                if (field.IsNullable)
                {
                    fileBlock.Add(1, string.Format("if (Convert.IsDBNull(reader[\"{0}\"])) {1} = null;", field.ColumnName, field.PropertyName));
                    fileBlock.Add(1, string.Format("else {0} = ({1})reader[\"{2}\"];", field.PropertyName, field.Type, field.ColumnName));
                }
                else
                    fileBlock.Add(1, string.Format("{0} = ({1})reader[\"{2}\"];", field.FieldName, field.TypeName, field.ColumnName));
            fileBlock.Add("}");

            return fileBlock;
        }

        private static IFileBlock FileBlock_GetWithPKMethod(Entity entity)
        {
            if (entity.PrimaryFields.Count == 0) return new StringFileBlock("");
            CompositeFileBlock fileBlock = new CompositeFileBlock();

            StringBuilder stringBuilder = new StringBuilder();
            foreach (Field field in entity.PrimaryFields)
            {
                stringBuilder.Append(",");
                stringBuilder.Append(field.TypeName);
                stringBuilder.Append(" ");
                stringBuilder.Append(field.ParameterName);
            }
            fileBlock.Add(string.Format("public static {0} GetWithPK({1})", entity.EntityName, stringBuilder.ToString().Substring(1)));
            fileBlock.Add("{");

            fileBlock.Add(1, "using (SqlConnection connection = DBConnection.Connection)");
            fileBlock.Add(1, "{");
            fileBlock.Add(2, "using (SqlCommand command = connection.CreateCommand())");
            fileBlock.Add(2, "{");
            List<string> parameters = new List<string>(entity.PrimaryFields.Count);
            foreach (Field field in entity.PrimaryFields)
                parameters.Add(string.Format("{0} = @{1}", field.ColumnName, field.ParameterName));


            fileBlock.Add(3, string.Format("command.CommandText = \"SELECT * FROM [{0}] WHERE {1}\";", entity.TableName, string.Join(" AND ", parameters.ToArray())));
            foreach (Field field in entity.PrimaryFields)
                fileBlock.Add(3,
                              string.Format("command.Parameters.Add(new SqlParameter(\"@{0}\" , {1}));", field.ParameterName, FormatNullableSqlParameter(field.ParameterName, field)));
            fileBlock.Add(3, "using (SqlDataReader reader = command.ExecuteReader(CommandBehavior.SingleRow))");
            fileBlock.Add(3, "{");
            fileBlock.Add(4, string.Format("if (reader.Read()) return new {0}(reader);", entity.EntityName));
            fileBlock.Add(4, "else return null;");
            fileBlock.Add(3, "}");
            fileBlock.Add(2, "}");
            fileBlock.Add(1, "}");
            fileBlock.Add("}");

            return fileBlock;
        }

        private static IFileBlock FileBlock_SaveMethod(Entity entity)
        {
            CompositeFileBlock fileBlock = new CompositeFileBlock();

            fileBlock.Add("public void Save()");
            fileBlock.Add("{");

            fileBlock.Add(1, "using (SqlConnection connection = DBConnection.Connection)");
            fileBlock.Add(1, "{");
            fileBlock.Add(2, "using (SqlCommand command = connection.CreateCommand())");
            fileBlock.Add(2, "{");
            StringBuilder stringBuilderSavedParams = new StringBuilder();
            StringBuilder stringBuilderUpdatedColumns = new StringBuilder();
            StringBuilder stringBuilderUpdateStatements = new StringBuilder();
            StringBuilder stringBuilderPK = new StringBuilder();
            foreach (Field field in entity.FieldCollection)
                if (!field.IsAutoGenerated)
                {
                    stringBuilderSavedParams.Append(string.Format(", @{0}", field.ParameterName));
                    stringBuilderUpdatedColumns.Append(string.Format(", [{0}]", field.ColumnName));
                    stringBuilderUpdateStatements.Append(string.Format(", [{0}] = @{1}", field.ColumnName, field.ParameterName));
                }
            foreach (Field field in entity.PrimaryFields)
                stringBuilderPK.Append(string.Format("AND [{0}] = @pk_{1}", field.ColumnName, field.ParameterName));

            fileBlock.Add(3, "if (_isNew)");
            fileBlock.Add(4,
                          string.Format("command.CommandText = \"INSERT INTO [{0}] ({1}) VALUES ({2}) \";",
                                        entity.TableName, stringBuilderUpdatedColumns.ToString().Substring(2),
                                        stringBuilderSavedParams.ToString().Substring(2)));
            fileBlock.Add(3, "else");
            fileBlock.Add(3, "{");
            if (entity.PrimaryFields.Count != 0)
            {
                fileBlock.Add(4,
                              string.Format("command.CommandText = \"UPDATE [{0}] ({1}) SET ({1}) WHERE {2} \";",
                                            entity.TableName, stringBuilderUpdateStatements.ToString().Substring(2),
                                            stringBuilderPK.ToString().Substring(4)));
                foreach (Field field in entity.PrimaryFields)
                    fileBlock.Add(4,
                                  string.Format("command.Parameters.Add(new SqlParameter(\"@pk_{0}\", {1}));",
                                                field.ParameterName, field.FieldName));
            } else
            {
                fileBlock.Add(4, "throw new NotImplementedException(\"Cannot update entity, because it doesn't have primary fields\");");
            }
            fileBlock.Add(3, "}");

            foreach (Field field in entity.FieldCollection)
                if (!field.IsAutoGenerated)
                    fileBlock.Add(3,
                                  string.Format("command.Parameters.Add(new SqlParameter(\"@{0}\", {1}));", field.ParameterName, FormatNullableSqlParameter(field.FieldName, field)));

            fileBlock.Add(3, "command.ExecuteNonQuery();");
            fileBlock.Add(3, "if (_isNew && false)");
            fileBlock.Add(3, "{");
            if (entity.PrimaryFields.Count != 0)
            {
                StringBuilder sbPK = new StringBuilder();
                foreach (Field field in entity.PrimaryFields)
                    sbPK.Append(", " + field.FieldName);
                fileBlock.Add(4,
                              string.Format("{0} entity = {0}.GetWithPK({1});", entity.EntityName,
                                            sbPK.ToString().Substring(2)));
                foreach (Field field in entity.FieldCollection)
                    fileBlock.Add(4, string.Format("this.{0} = entity.{0};", field.FieldName));
            }
            fileBlock.Add(4, "_isNew = false;");
            fileBlock.Add(3, "}");

            fileBlock.Add(2, "}");
            fileBlock.Add(1, "}");
            fileBlock.Add("}");

            return fileBlock;
        }

        private static string FormatNullableSqlParameter(string fieldName, Field field)
        {
            if (!field.IsNullable)
                return fieldName;
            if (field.Type.IsValueType)
                return string.Format("{0}.HasValue ? (object){0}.Value : (object)System.DBNull.Value", fieldName);
            return string.Format("{0} == null ? (object){0} : (object)System.DBNull.Value", fieldName);
        }
    }
}